page.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. "use client";
  2. import {fetchApi, fetchFile} from "@/app/_modules/func";
  3. import {DeleteOutlined, ExclamationCircleFilled, EyeOutlined, ReloadOutlined,} from "@ant-design/icons";
  4. import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
  5. import {PageContainer, ProDescriptions, ProTable,} from "@ant-design/pro-components";
  6. import {Button, message, Modal, Space, Tag} from "antd";
  7. import {useRouter} from "next/navigation";
  8. import {faCheck, faDownload, faToggleOff, faToggleOn, faXmark,} from "@fortawesome/free-solid-svg-icons";
  9. import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
  10. import {useRef, useState} from "react";
  11. //查询Job详情
  12. const queryJobAPI = "/api/monitor/job";
  13. //查询表格数据API
  14. const queryAPI = "/api/monitor/jobLog/list";
  15. //删除API
  16. const deleteAPI = "/api/monitor/jobLog";
  17. //导出API
  18. const exportAPI = "/api/monitor/jobLog/export";
  19. //导出文件前缀名
  20. const exportFilePrefix = "joblog";
  21. //清空调度日志API
  22. const clearAllAPI = "/api/monitor/jobLog/clean";
  23. export default function JobLog({ params }: { params: { jobid: string } }) {
  24. const { push } = useRouter();
  25. // 添加用于控制删除确认模态框的状态
  26. const [deleteModalVisible, setDeleteModalVisible] = useState(false);
  27. const [deleteJobLogId, setDeleteJobLogId] = useState<string | number | null>(null);
  28. // 添加用于控制清空确认模态框的状态
  29. const [clearAllModalVisible, setClearAllModalVisible] = useState(false);
  30. //获取对应的任务的JobName的值
  31. const getJobName = async () => {
  32. const resp = await fetchApi(`${queryJobAPI}/${params.jobid}`, push);
  33. if (resp != undefined) {
  34. if (searchTableFormRef.current) {
  35. searchTableFormRef.current.setFieldsValue({
  36. jobName: resp.data.jobName,
  37. jobGroup: resp.data.jobGroup,
  38. });
  39. }
  40. return [resp.data.jobName, resp.data.jobGroup];
  41. }
  42. return "";
  43. };
  44. //表格列定义
  45. const columns: ProColumns[] = [
  46. {
  47. title: "日志编号",
  48. dataIndex: "jobLogId",
  49. search: false,
  50. },
  51. {
  52. title: "任务名称",
  53. fieldProps: {
  54. placeholder: "请输入任务名称",
  55. },
  56. dataIndex: "jobName",
  57. ellipsis: true,
  58. order: 4,
  59. },
  60. {
  61. title: "任务组名",
  62. dataIndex: "jobGroup",
  63. valueType: "select",
  64. valueEnum: {
  65. DEFAULT: {
  66. text: "默认",
  67. status: "DEFAULT",
  68. },
  69. SYSTEM: {
  70. text: "系统",
  71. status: "SYSTEM",
  72. },
  73. },
  74. order: 3,
  75. },
  76. {
  77. title: "调用目标字符串",
  78. dataIndex: "invokeTarget",
  79. ellipsis: true,
  80. search: false,
  81. },
  82. {
  83. title: "日志信息",
  84. dataIndex: "jobMessage",
  85. ellipsis: true,
  86. search: false,
  87. },
  88. {
  89. title: "执行状态",
  90. dataIndex: "status",
  91. valueType: "select",
  92. render: (_, record) => {
  93. return (
  94. <Space>
  95. <Tag
  96. color={record.status === "0" ? "green" : "red"}
  97. icon={
  98. record.status == 0 ? (
  99. <FontAwesomeIcon icon={faCheck} />
  100. ) : (
  101. <FontAwesomeIcon icon={faXmark} />
  102. )
  103. }
  104. >
  105. {_}
  106. </Tag>
  107. </Space>
  108. );
  109. },
  110. valueEnum: {
  111. 0: {
  112. text: "成功",
  113. status: "0",
  114. },
  115. 1: {
  116. text: "失败",
  117. status: "1",
  118. },
  119. },
  120. order: 2,
  121. },
  122. {
  123. title: "执行时间",
  124. dataIndex: "createTime",
  125. valueType: "dateTime",
  126. search: false,
  127. },
  128. {
  129. title: "执行时间",
  130. fieldProps: {
  131. placeholder: ["开始日期", "结束日期"],
  132. },
  133. dataIndex: "createTimeRange",
  134. valueType: "dateRange",
  135. hideInTable: true,
  136. order: 1,
  137. search: {
  138. transform: (value) => {
  139. return {
  140. "params[beginTime]": `${value[0]} 00:00:00`,
  141. "params[endTime]": `${value[1]} 23:59:59`,
  142. };
  143. },
  144. },
  145. },
  146. {
  147. title: "操作",
  148. key: "option",
  149. search: false,
  150. render: (_, record) => [
  151. <Button
  152. key="detailBtn"
  153. type="link"
  154. icon={<EyeOutlined />}
  155. onClick={() => onClickShowRowDetailModal(record)}
  156. >
  157. 详情
  158. </Button>,
  159. ],
  160. },
  161. ];
  162. //0.查询表格数据
  163. const queryTableData = async (params: any, sorter: any, filter: any) => {
  164. const searchParams = {
  165. pageNum: params.current,
  166. ...params,
  167. };
  168. delete searchParams.current;
  169. const queryParams = new URLSearchParams(searchParams);
  170. //如果没有带上默认的字典类型,查询绑定上
  171. if (!("jobName" in searchParams)) {
  172. const result = await getJobName();
  173. queryParams.append("jobName", result[0]);
  174. queryParams.append("jobGroup", result[1]);
  175. }
  176. Object.keys(sorter).forEach((key) => {
  177. queryParams.append("orderByColumn", key);
  178. if (sorter[key] === "ascend") {
  179. queryParams.append("isAsc", "ascending");
  180. } else {
  181. queryParams.append("isAsc", "descending");
  182. }
  183. });
  184. const body = await fetchApi(`${queryAPI}?${queryParams}`, push);
  185. return body;
  186. };
  187. //操作当前数据的附加数据
  188. const [operatRowData, setOperateRowData] = useState<{
  189. [key: string]: any;
  190. }>({});
  191. //3.删除
  192. //点击删除按钮,展示删除确认框
  193. const onClickDeleteRow = (record?: any) => {
  194. const jobLogId = record !== undefined ? record.jobLogId : selectedRowKeys.join(",");
  195. setDeleteJobLogId(jobLogId);
  196. setDeleteModalVisible(true);
  197. };
  198. //确定删除选中的数据
  199. const executeDeleteRow = async () => {
  200. if (deleteJobLogId === null) return;
  201. const body = await fetchApi(`${deleteAPI}/${deleteJobLogId}`, push, {
  202. method: "DELETE",
  203. });
  204. if (body !== undefined) {
  205. if (body.code == 200) {
  206. message.success("删除成功");
  207. //删除按钮变回不可点击
  208. setRowCanDelete(false);
  209. //选中行数据重置为空
  210. setSelectedRowKeys([]);
  211. //刷新列表
  212. if (actionTableRef.current) {
  213. actionTableRef.current.reload();
  214. }
  215. } else {
  216. message.error(body.msg);
  217. }
  218. }
  219. setDeleteModalVisible(false);
  220. setDeleteJobLogId(null);
  221. };
  222. //取消删除操作
  223. const cancelDeleteRow = () => {
  224. setDeleteModalVisible(false);
  225. setDeleteJobLogId(null);
  226. };
  227. //弹出清空确认框
  228. const onClickClearAll = () => {
  229. setClearAllModalVisible(true);
  230. };
  231. //执行清空调度日志
  232. const executeClearAll = async () => {
  233. const body = await fetchApi(clearAllAPI, push, {
  234. method: "DELETE",
  235. });
  236. if (body !== undefined) {
  237. if (body.code == 200) {
  238. message.success("清空成功");
  239. if (actionTableRef.current) {
  240. actionTableRef.current.reload();
  241. }
  242. } else {
  243. message.error(body.msg);
  244. }
  245. } else {
  246. message.error("清空发生异常");
  247. }
  248. setClearAllModalVisible(false);
  249. };
  250. //取消清空操作
  251. const cancelClearAll = () => {
  252. setClearAllModalVisible(false);
  253. };
  254. //4.导出
  255. //导出表格数据
  256. const exportTable = async () => {
  257. if (searchTableFormRef.current) {
  258. const formData = new FormData();
  259. const data = {
  260. pageNum: page,
  261. pageSize: pageSize,
  262. ...searchTableFormRef.current.getFieldsValue(),
  263. };
  264. Object.keys(data).forEach((key) => {
  265. if (data[key] !== undefined) {
  266. formData.append(key, data[key]);
  267. }
  268. });
  269. await fetchFile(
  270. exportAPI,
  271. push,
  272. {
  273. method: "POST",
  274. body: formData,
  275. },
  276. `${exportFilePrefix}_${new Date().getTime()}.xlsx`
  277. );
  278. }
  279. };
  280. //5.选择行
  281. //选中行操作
  282. const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
  283. const [selectedRow, setSelectedRow] = useState(undefined as any);
  284. //删除按钮是否可用,选中行时才可用
  285. const [rowCanDelete, setRowCanDelete] = useState(false);
  286. //ProTable rowSelection
  287. const rowSelection = {
  288. onChange: (newSelectedRowKeys: React.Key[], selectedRows: any[]) => {
  289. setSelectedRowKeys(newSelectedRowKeys);
  290. setRowCanDelete(newSelectedRowKeys && newSelectedRowKeys.length > 0);
  291. if (newSelectedRowKeys && newSelectedRowKeys.length == 1) {
  292. setSelectedRow(selectedRows[0]);
  293. } else {
  294. setSelectedRow(undefined);
  295. }
  296. },
  297. //复选框的额外禁用判断
  298. // getCheckboxProps: (record) => ({
  299. // disabled: record.userId == 1,
  300. // }),
  301. };
  302. //搜索栏显示状态
  303. const [showSearch, setShowSearch] = useState(true);
  304. //action对象引用
  305. const actionTableRef = useRef<ActionType>(null);
  306. //搜索表单对象引用
  307. const searchTableFormRef = useRef<ProFormInstance>(null!);
  308. //当前页数和每页条数
  309. const [page, setPage] = useState(1);
  310. const defaultPageSize = 10;
  311. const [pageSize, setPageSize] = useState(defaultPageSize);
  312. const pageChange = (page: number, pageSize: number) => {
  313. setPage(page);
  314. setPageSize(pageSize);
  315. };
  316. const [isShowDetail, setIsShowDetail] = useState(false);
  317. //展示详情框
  318. const onClickShowRowDetailModal = (record: any) => {
  319. setIsShowDetail(true);
  320. setSelectedRow(record);
  321. };
  322. return (
  323. <PageContainer
  324. header={{
  325. title: "调度日志",
  326. onBack(e) {
  327. push("/monitor/job");
  328. },
  329. }}
  330. >
  331. <ProTable
  332. formRef={searchTableFormRef}
  333. rowKey="jobLogId"
  334. rowSelection={{
  335. selectedRowKeys,
  336. ...rowSelection,
  337. }}
  338. columns={columns}
  339. request={async (params: any, sorter: any, filter: any) => {
  340. // 表单搜索项会从 params 传入,传递给后端接口。
  341. const data = await queryTableData(params, sorter, filter);
  342. if (data !== undefined) {
  343. return Promise.resolve({
  344. data: data.rows,
  345. success: true,
  346. total: data.total,
  347. });
  348. }
  349. return Promise.resolve({
  350. data: [],
  351. success: true,
  352. });
  353. }}
  354. pagination={{
  355. defaultPageSize: defaultPageSize,
  356. showQuickJumper: true,
  357. showSizeChanger: true,
  358. onChange: pageChange,
  359. }}
  360. search={
  361. showSearch
  362. ? {
  363. defaultCollapsed: false,
  364. searchText: "搜索",
  365. }
  366. : false
  367. }
  368. dateFormatter="string"
  369. actionRef={actionTableRef}
  370. toolbar={{
  371. actions: [
  372. <Button
  373. key="danger"
  374. danger
  375. icon={<DeleteOutlined />}
  376. disabled={!rowCanDelete}
  377. onClick={() => onClickDeleteRow()}
  378. >
  379. 删除
  380. </Button>,
  381. <Button
  382. key="danger"
  383. danger
  384. icon={<DeleteOutlined />}
  385. onClick={() => onClickClearAll()}
  386. >
  387. 清空
  388. </Button>,
  389. <Button
  390. key="export"
  391. type="primary"
  392. icon={<FontAwesomeIcon icon={faDownload} />}
  393. onClick={exportTable}
  394. >
  395. 导出
  396. </Button>,
  397. ],
  398. settings: [
  399. {
  400. key: "switch",
  401. icon: showSearch ? (
  402. <FontAwesomeIcon icon={faToggleOn} />
  403. ) : (
  404. <FontAwesomeIcon icon={faToggleOff} />
  405. ),
  406. tooltip: showSearch ? "隐藏搜索栏" : "显示搜索栏",
  407. onClick: (key: string | undefined) => {
  408. setShowSearch(!showSearch);
  409. },
  410. },
  411. {
  412. key: "refresh",
  413. tooltip: "刷新",
  414. icon: <ReloadOutlined />,
  415. onClick: (key: string | undefined) => {
  416. if (actionTableRef.current) {
  417. actionTableRef.current.reload();
  418. }
  419. },
  420. },
  421. ],
  422. }}
  423. />
  424. {selectedRow !== undefined && (
  425. <Modal
  426. title="调度日志详情"
  427. footer={<Button onClick={() => setIsShowDetail(false)}>关闭</Button>}
  428. open={isShowDetail}
  429. onCancel={() => setIsShowDetail(false)}
  430. >
  431. <ProDescriptions column={2}>
  432. <ProDescriptions.Item label="日志序号">
  433. {selectedRow.jobLogId}
  434. </ProDescriptions.Item>
  435. <ProDescriptions.Item
  436. label="任务分组"
  437. valueEnum={{
  438. DEFAULT: {
  439. text: "默认",
  440. status: "DEFAULT",
  441. },
  442. SYSTEM: {
  443. text: "系统",
  444. status: "SYSTEM",
  445. },
  446. }}
  447. >
  448. {selectedRow.jobGroup}
  449. </ProDescriptions.Item>
  450. <ProDescriptions.Item label="任务名称">
  451. {selectedRow.jobName}
  452. </ProDescriptions.Item>
  453. <ProDescriptions.Item label="执行时间">
  454. {selectedRow.createTime}
  455. </ProDescriptions.Item>
  456. </ProDescriptions>
  457. <ProDescriptions column={1}>
  458. <ProDescriptions.Item label="调用目标方法">
  459. {selectedRow.invokeTarget}
  460. </ProDescriptions.Item>
  461. <ProDescriptions column={1}>
  462. <ProDescriptions.Item label="日志信息">
  463. {selectedRow.jobMessage}
  464. </ProDescriptions.Item>
  465. <ProDescriptions.Item
  466. label="执行状态"
  467. valueEnum={{
  468. 0: {
  469. text: "正常",
  470. status: "0",
  471. },
  472. 1: {
  473. text: "暂停",
  474. status: "1",
  475. },
  476. }}
  477. >
  478. {selectedRow.status}
  479. </ProDescriptions.Item>
  480. </ProDescriptions>
  481. </ProDescriptions>
  482. </Modal>
  483. )}
  484. {/* 删除确认模态框 */}
  485. <Modal
  486. title={
  487. <div style={{ display: 'flex', alignItems: 'center' }}>
  488. <ExclamationCircleFilled style={{ color: '#faad14', marginRight: 8 }} />
  489. <span>系统提示</span>
  490. </div>
  491. }
  492. open={deleteModalVisible}
  493. onOk={executeDeleteRow}
  494. onCancel={cancelDeleteRow}
  495. okText="确认"
  496. cancelText="取消"
  497. >
  498. <p>{`确定删除调度日志编号为“${deleteJobLogId}”的数据项?`}</p>
  499. </Modal>
  500. {/* 清空确认模态框 */}
  501. <Modal
  502. title={
  503. <div style={{ display: 'flex', alignItems: 'center' }}>
  504. <ExclamationCircleFilled style={{ color: '#faad14', marginRight: 8 }} />
  505. <span>系统提示</span>
  506. </div>
  507. }
  508. open={clearAllModalVisible}
  509. onOk={executeClearAll}
  510. onCancel={cancelClearAll}
  511. okText="确认"
  512. cancelText="取消"
  513. >
  514. <p>确定清空所有调度日志数据项?</p>
  515. </Modal>
  516. </PageContainer>
  517. );
  518. }